﻿using Adnc.Application.Shared.Services;
using Adnc.Infra.Helper;
using Adnc.Infra.IRepositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WYT.Usr.Application.Contracts.Dtos.Health;
using WYT.Usr.Application.Contracts.Services;
using WYT.Usr.Repository.Entities;

namespace WYT.Usr.Application.Services
{
    /// <summary>
    /// 健康自检服务
    /// </summary>
    public class HealthAppService : AbstractAppService, IHealthAppService
    {
        private readonly IEfRepository<HealthExam> _healthExamRepository;
        private readonly IEfRepository<HealthAnswer> _healthAnswerRepository;
        private readonly IEfRepository<HealthEvaluationLog> _healthEvaluationLogRepository;
        private readonly IEfRepository<HealthEvaluationExamAnswer> _healthUserExamAnswerRepository;
        private readonly IEfRepository<HealthAnswerDeduct> _healthAnswerDeductRepository;
        private readonly IEfRepository<HealthAnswerTargetRate> _healthAnswerTargetRateRepository;
        private readonly IEfRepository<HealthRiskResult> _healthRiskResultRepository;
        private readonly IEfRepository<HealthEvaluationRsult> _healthEvaluationRsultRepository;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="healthExamRepository"></param>
        /// <param name="healthAnswerRepository"></param>
        /// <param name="healthEvaluationLogRepository"></param>
        /// <param name="healthUserExamAnswerRepository"></param>
        /// <param name="healthAnswerDeductRepository"></param>
        /// <param name="healthAnswerTargetRateRepository"></param>
        /// <param name="healthRiskResultRepository"></param>
        /// <param name="healthEvaluationRsultRepository"></param>
        public HealthAppService(IEfRepository<HealthExam> healthExamRepository,
            IEfRepository<HealthAnswer> healthAnswerRepository,
            IEfRepository<HealthEvaluationLog> healthEvaluationLogRepository,
            IEfRepository<HealthEvaluationExamAnswer> healthUserExamAnswerRepository,
            IEfRepository<HealthAnswerDeduct> healthAnswerDeductRepository,
            IEfRepository<HealthAnswerTargetRate> healthAnswerTargetRateRepository,
            IEfRepository<HealthRiskResult> healthRiskResultRepository,
            IEfRepository<HealthEvaluationRsult> healthEvaluationRsultRepository)
        {
            _healthAnswerRepository = healthAnswerRepository;
            _healthExamRepository = healthExamRepository;
            _healthEvaluationLogRepository = healthEvaluationLogRepository;
            _healthUserExamAnswerRepository = healthUserExamAnswerRepository;
            _healthAnswerDeductRepository = healthAnswerDeductRepository;
            _healthAnswerTargetRateRepository = healthAnswerTargetRateRepository;
            _healthRiskResultRepository = healthRiskResultRepository;
            _healthEvaluationRsultRepository = healthEvaluationRsultRepository;
        }

        /// <summary>
        /// 开始答题
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="classify"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task<AppSrvResult<StartAnswerDto>> StartAnswer(long userId, int classify = 1)
        {
            var log = new HealthEvaluationLog()
            {
                Id = IdGenerater.GetNextId(),
                Classify = classify,
                User = userId,
                Sex = String.Empty,
                EvaluationTime = DateTime.Now,
            };
            await _healthEvaluationLogRepository.InsertAsync(log);

            StartAnswerDto result = new StartAnswerDto() { Id = log.Id, Exams = new List<ExamAnswersDto>() };
            var exams = _healthExamRepository.Where(x => x.Classify == classify && x.IsDelete == false)
                .OrderBy(x => x.Sort)
                .ToList();

            var ids = exams.Select(x => x.Id).ToList();
            var answers = _healthAnswerRepository.Where(x => ids.Contains(x.Exam)).ToList();
            exams.ForEach(x =>
            {
                result.Exams.Add(new ExamAnswersDto
                {
                    Exam = Mapper.Map<HealthExamDto>(x),
                    Answers = answers.FindAll(c => c.Exam == x.Id)
                    .OrderBy(c => c.Sort)
                    .Select(c => new HealthAnswerDto
                    {
                        Id = c.Id,
                        Content = c.Content,
                        Number = c.Number,
                    }).ToList()
                });
            });
            return result;
        }

        /// <summary>
        /// 提交答案
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<bool>> UserAnswer(UserAnswerParaDto input)
        {
            var log = await _healthEvaluationLogRepository.FindAsync(input.id);
            if (log == null || log.SubmitTime.HasValue)
                return Problem(null, "参数错误或者重复提交!");

            List<HealthEvaluationExamAnswer> answers = new List<HealthEvaluationExamAnswer>();
            foreach (var item in input.answer)
            {
                var exaInfo = await _healthExamRepository.FindAsync(item.eid);
                if (exaInfo == null)
                    return Problem(null, "参数错误!");
                switch (exaInfo.Type)
                {
                    case 1:
                        if (string.IsNullOrEmpty(item.text))
                            return Problem(null, "参数错误!");

                        answers.Add(new HealthEvaluationExamAnswer()
                        {
                            Id = IdGenerater.GetNextId(),
                            EvaluationLog = input.id,
                            Exam = item.eid,
                            Answer = item.text,
                            Time = DateTime.Now,
                        });
                        break;
                    case 2:
                        if (item.aid <= 0)
                            return Problem(null, "参数错误!");

                        answers.Add(new HealthEvaluationExamAnswer()
                        {
                            Id = IdGenerater.GetNextId(),
                            EvaluationLog = input.id,
                            Exam = item.eid,
                            Answer = item.aid.ToString(),
                            Time = DateTime.Now,
                        });
                        break;
                    case 3:
                        if (item.aids == null || item.aids.Count == 0 || item.aids.Any(x => x <= 0))
                            return Problem(null, "参数错误!");
                        answers.Add(new HealthEvaluationExamAnswer()
                        {
                            Id = IdGenerater.GetNextId(),
                            EvaluationLog = input.id,
                            Exam = item.eid,
                            Answer = String.Join(",", item.aids),
                            Time = DateTime.Now,
                        });
                        break;
                    case 4:
                        break;
                }
            }
            log.SubmitTime = DateTime.Now;
            await _healthUserExamAnswerRepository.InsertRangeAsync(answers);
            await _healthEvaluationLogRepository.UpdateAsync(log, UpdatingProps<HealthEvaluationLog>(x => x.SubmitTime));
            return true;
        }

        /// <summary>
        /// 生产报告
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<AppSrvResult<HealthReportDto>> GetHealthUserExam(long id)
        {
            var totalPoints = 100;
            var log = await _healthEvaluationLogRepository.FindAsync(id);
            if (log == null)
                return Problem(null, "参数错误");
            if (!log.ReportTime.HasValue)
            {
                var examAnswer = await _healthUserExamAnswerRepository.Where(x => x.EvaluationLog == id).ToListAsync();
                var examIds = examAnswer.Select(x => x.Exam).ToList();
                var exams = await _healthExamRepository.Where(x => examIds.Contains(x.Id)).ToListAsync();
                var answers = await _healthAnswerRepository.Where(x => examIds.Contains(x.Exam)).ToListAsync();

                var userAnswer = (from ea in examAnswer
                                  join e in exams on ea.Exam equals e.Id
                                  select new
                                  {
                                      Exam = e.Id,
                                      e.Type,
                                      e.BaseType,
                                      e.TallestDeduct,
                                      ea.Answer
                                  }).ToList();

                //年龄
                var age = int.Parse(userAnswer.First(x => x.BaseType == 1).Answer);
                //性别
                var sex = answers.First(x => x.Id == long.Parse(userAnswer.First(c => c.BaseType == 2).Answer)).Content;
                //体重
                var weight = double.Parse(userAnswer.First(x => x.BaseType == 3).Answer);
                //身高
                var stature = double.Parse(userAnswer.First(x => x.BaseType == 4).Answer);
                //bmi
                // double bmi = weight / (stature / Double.Parse("100")) / (stature /  Double.Parse("100"));
                double bmi = double.Parse((weight / Math.Pow((stature / Double.Parse("100")), 2)).ToString("0.00"));
                if (bmi < 18.5)
                    totalPoints -= 3; //过轻
                else if (bmi >= 24 && bmi <= 27.9)
                    totalPoints -= 3; //超重
                else if (bmi >= 28)
                    totalPoints -= 5; //肥胖
                                      //bmr
                double bmr = 0.00;
                switch (sex)
                {
                    case "男":
                        bmr = (9.99 * weight) + (6.25 * stature) - (4.92 * age) + 5;
                        if (bmr < 1442.83)
                            totalPoints -= 5; //过低
                        else if (bmr > 1647.53)
                            totalPoints -= 5; //过高
                        break;
                    case "女":
                        bmr = (9.99 * weight) + (6.25 * stature) - (4.92 * age) - 161;
                        if (bmr < 1154.37)
                            totalPoints -= 5; //过低
                        else if (bmr > 1415.19)
                            totalPoints -= 5; //过高
                        break;
                }

                var deducts = await _healthAnswerDeductRepository.Where(x => examIds.Contains(x.Exam)).ToListAsync();
                var rates = await _healthAnswerTargetRateRepository.Where(x => examIds.Contains(x.Exam)).ToListAsync();
                var risks = await _healthRiskResultRepository.GetAll().Select(x => new HealthRiskHitRatioDto()
                {
                    ID = x.Id,
                    Percentage = 0,
                    RiskResult = Mapper.Map<HealthRiskResultDto>(x),
                    Sex = sex,
                    Age = age,
                    Bmi = bmi,
                    Bmr = bmr
                }).ToListAsync();

                //扣分
                foreach (var item in userAnswer)
                {
                    if (item.BaseType.HasValue)
                        continue;
                    //单选题直接扣分
                    if (item.Type == 2)
                    {
                        var deduct = deducts.FirstOrDefault(x => x.Exam == item.Exam && x.Answer == long.Parse(item.Answer));
                        if (deduct != null)
                            totalPoints -= deduct.Score;
                        var rate = rates.FirstOrDefault(x => x.Exam == item.Exam && x.Answer == long.Parse(item.Answer));
                        if (rate != null)
                            risks.First(x => x.ID == rate.RiskResult).Percentage += rate.Rate;
                    }
                    //多选题计算后扣分
                    else if (item.Type == 3)
                    {
                        var ids = item.Answer.ToLongArray(",");
                        var score = deducts.FindAll(x => x.Exam == item.Exam && ids.Contains(x.Answer)).Sum(x => x.Score);
                        if(!item.TallestDeduct.HasValue)
                            score = 0;
                        else if(score > item.TallestDeduct.Value)
                            score = item.TallestDeduct.Value;
                        totalPoints -= score;

                        var ur = rates.FindAll(x => x.Exam == item.Exam && ids.Contains(x.Answer)).ToList();
                        if (ur.Count > 0)
                        {
                            var riskIds = ur.Select(x => x.RiskResult).Distinct().ToList();
                            foreach (var risk in riskIds)
                            {
                                var maxRate = ur.FindAll(x => x.RiskResult == risk).OrderByDescending(x => x.MaxRate).First().MaxRate;
                                var rate = ur.Sum(x => x.Rate);
                                if (rate >= 100)
                                    rate = 100;
                                else if (rate > maxRate)
                                    rate = maxRate;
                                risks.First(x => x.ID == risk).Percentage += rate;
                            }
                        }

                    }
                }

                var riskHitRatioViews = risks.OrderByDescending(x => x.Percentage)
                    .Select(x =>
                        new HealthRiskDto
                        {
                            Id = x.ID,
                            Percentage = x.Percentage,
                            Type = x.RiskResult.Type,
                            Ico = x.RiskResult.Ico,
                            Describe = x.RiskResult.Describe,
                            Influencing = x.RiskResult.Influencing,
                            Riskhint = x.RiskResult.Riskhint,
                            Symptom = x.RiskResult.Symptom,
                            JYYY = x.RiskResult.JYYY,
                            JYSY = x.RiskResult.JYSY
                        });
                log.TotalPoints = totalPoints;
                log.Bmi = decimal.Parse(bmi.ToString());
                log.Bmr = decimal.Parse(bmr.ToString());
                log.Age = age;
                log.Sex = sex;
                log.ReportTime = DateTime.Now;

                await _healthEvaluationLogRepository.UpdateAsync(log, UpdatingProps<HealthEvaluationLog>(x => x.TotalPoints, x => x.Bmi, x => x.Bmr, x => x.Age, x => x.Sex, x => x.ReportTime));

                var healthEvaluationRsults = new List<HealthEvaluationRsult>();
                riskHitRatioViews.ForEach(x =>
                {
                    healthEvaluationRsults.Add(new HealthEvaluationRsult
                    {
                        Id = IdGenerater.GetNextId(),
                        EvaluationLog = id,
                        RiskResult = x.Id,
                        Percentage = x.Percentage
                    });                    
                });
                await _healthEvaluationRsultRepository.InsertRangeAsync(healthEvaluationRsults);
                riskHitRatioViews = riskHitRatioViews.Take(3);
                return new HealthReportDto
                {
                    Points = totalPoints,
                    BMI = decimal.Parse(bmi.ToString()),
                    BMR = decimal.Parse(bmr.ToString()),
                    Age = age,
                    Sex = sex,
                    Risks = riskHitRatioViews.ToList()
                };
            }
            else
            {
                var evaluationRsult = await _healthEvaluationRsultRepository.Where(x => x.EvaluationLog == id).OrderByDescending(x => x.Percentage).Take(3).ToListAsync();
                var ids = evaluationRsult.Select(x => x.RiskResult).ToList();
                var riskResult = await _healthRiskResultRepository.Where(x => ids.Contains(x.Id)).ToListAsync();

                var risks = (from r in riskResult
                 join er in evaluationRsult on r.Id equals er.RiskResult
                 select new HealthRiskDto
                 {
                     Id = r.Id,
                     Ico = r.Ico,
                     Percentage = er.Percentage,
                     Describe = r.Describe,
                     Influencing = r.Influencing,
                     JYSY = r.JYSY,
                     JYYY = r.JYYY,
                     Riskhint = r.Riskhint,
                     Symptom = r.Symptom,
                     Type = r.Type
                 }).ToList();

                return new HealthReportDto { 
                    Points =  log.TotalPoints,
                    Age = log.Age,
                    BMI = log.Bmi,
                    BMR = log.Bmr,
                    Sex = log.Sex,
                    Risks = risks,
                };
            }
        }
    }
}
